/**
 * Copyright (c) 2016-2021 by the respective copyright holders.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package com.zsmartsystems.zigbee.dongle.ember.autocode;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.zsmartsystems.zigbee.dongle.ember.autocode.xml.Command;
import com.zsmartsystems.zigbee.dongle.ember.autocode.xml.Enumeration;
import com.zsmartsystems.zigbee.dongle.ember.autocode.xml.Parameter;
import com.zsmartsystems.zigbee.dongle.ember.autocode.xml.Protocol;
import com.zsmartsystems.zigbee.dongle.ember.autocode.xml.Structure;
import com.zsmartsystems.zigbee.dongle.ember.autocode.xml.Value;

/**
 *
 * @author Chris Jackson
 *
 */
public class CommandGenerator extends ClassGenerator {
    protected final String ashPackage = "com.zsmartsystems.zigbee.dongle.ember.internal.ash";
    protected final String ezspPackage = "com.zsmartsystems.zigbee.dongle.ember.ezsp";
    protected final String serializerPackage = "com.zsmartsystems.zigbee.dongle.ember.internal.serializer";
    protected final String ezspCommandPackage = "com.zsmartsystems.zigbee.dongle.ember.ezsp.command";
    protected final String ezspStructurePackage = "com.zsmartsystems.zigbee.dongle.ember.ezsp.structure";

    public void go(Protocol protocol) throws FileNotFoundException, UnsupportedEncodingException {
        String className;
        for (Command command : protocol.commands) {
            if (command.name.endsWith("Handler")) {
                className = "Ezsp" + command.name.substring(0, 1).toUpperCase() + command.name.substring(1);
                createCommandClass(className, command, command.response_parameters);
            } else {
                className = "Ezsp" + upperCaseFirstCharacter(command.name) + "Request";
                createCommandClass(className, command, command.command_parameters);

                className = "Ezsp" + command.name.substring(0, 1).toUpperCase() + command.name.substring(1)
                        + "Response";
                createCommandClass(className, command, command.response_parameters);
            }
        }

        createEmberFrame(protocol);

        for (Structure structure : protocol.structures) {
            createStructureClass(structure);
        }

        for (Enumeration enumeration : protocol.enumerations) {
            createEnumClass(enumeration);
        }
    }

    private void createCommandClass(String className, Command command, List<Parameter> parameters)
            throws FileNotFoundException, UnsupportedEncodingException {

        System.out.println("Processing command class " + command.name + "  [" + className + "()]");

        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter, false, "UTF-8");

        clearImports();
        // addImport("org.slf4j.Logger");
        // addImport("org.slf4j.LoggerFactory");

        // addImport("java.util.Map");
        // addImport("java.util.HashMap");

        out.println("/**");
        out.println(" * Class to implement the Ember EZSP command <b>" + command.name + "</b>.");
        out.println(" * <p>");
        outputWithLinebreak(out, "", command.description);
        out.println(" * <p>");
        out.println(" * This class provides methods for processing EZSP commands.");
        out.println(" * <p>");
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson - Initial contribution of Java code generator");
        out.println(" */");

        if (className.endsWith("Request")) {
            addImport(ezspPackage + ".EzspFrameRequest");
            out.println("public class " + className + " extends EzspFrameRequest {");
        } else {
            addImport(ezspPackage + ".EzspFrameResponse");
            out.println("public class " + className + " extends EzspFrameResponse {");
        }

        out.println("    public static final int FRAME_ID = " + String.format("0x%02X", command.id) + ";");

        // out.println(" private static final Logger logger = LoggerFactory.getLogger(" + className + ".class);");

        for (Parameter parameter : parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println();
            out.println("    /**");
            if (parameter.description.length() != 0) {
                outputWithLinebreak(out, "    ", parameter.description);
                out.println("     * <p>");
            }
            out.println("     * EZSP type is <i>" + parameter.data_type + "</i> - Java type is {@link "
                    + getTypeClass(parameter.data_type) + "}");
            out.println("     */");
            out.println("    private " + getTypeClass(parameter.data_type) + " " + parameter.name + ";");
        }

        if (className.endsWith("Request")) {
            addImport(serializerPackage + ".EzspSerializer");
            out.println();
            out.println("    /**");
            out.println("     * Serialiser used to serialise to binary line data");
            out.println("     */");
            out.println("    private EzspSerializer serializer;");
            out.println();
            out.println("    /**");
            out.println("     * Request constructor");
            out.println("     */");
            out.println("    public " + className + "() {");
            out.println("        frameId = FRAME_ID;");
            out.println("        serializer = new EzspSerializer();");
            out.println("    }");
        } else {
            out.println();
            out.println("    /**");
            out.println("     * Response and Handler constructor");
            out.println("     */");
            out.println("    public " + className + "(int[] inputBuffer) {");
            out.println("        // Super creates deserializer and reads header fields");
            out.println("        super(inputBuffer);");
            out.println();
            out.println("        // Deserialize the fields");
            Map<String, String> autoSizers = new HashMap<String, String>();
            for (Parameter parameter : parameters) {
                if (parameter.auto_size != null) {
                    out.println("        int " + parameter.name + " = deserializer.deserialize"
                            + getTypeSerializer(parameter.data_type) + "();");
                    autoSizers.put(parameter.auto_size, parameter.name);
                    continue;
                }
                if (autoSizers.get(parameter.name) != null) {
                    out.println("        " + parameter.name + "= deserializer.deserialize"
                            + getTypeSerializer(parameter.data_type) + "(" + autoSizers.get(parameter.name) + ");");
                    continue;
                }
                if (parameter.data_type.contains("[") && parameter.data_type.contains("]")
                        && !parameter.data_type.contains("[]")) {
                    int length = Integer.parseInt(parameter.data_type.substring(parameter.data_type.indexOf("[") + 1,
                            parameter.data_type.indexOf("]")));
                    out.println("        " + parameter.name + " = deserializer.deserialize"
                            + getTypeSerializer(parameter.data_type) + "(" + length + ");");
                    continue;
                }
                out.println("        " + parameter.name + " = deserializer.deserialize"
                        + getTypeSerializer(parameter.data_type) + "();");
            }
            out.println("    }");
        }

        for (Parameter parameter : parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println();
            out.println("    /**");
            if (parameter.description.length() != 0) {
                outputWithLinebreak(out, "    ", parameter.description);
                out.println("     * <p>");
            }
            out.println("     * EZSP type is <i>" + parameter.data_type + "</i> - Java type is {@link "
                    + getTypeClass(parameter.data_type) + "}");
            out.println("     *");
            out.println("     * @return the current " + parameter.name + " as {@link "
                    + getTypeClass(parameter.data_type) + "}");
            out.println("     */");
            out.println("    public " + getTypeClass(parameter.data_type) + " get"
                    + upperCaseFirstCharacter(parameter.name) + "() {");
            out.println("        return " + parameter.name + ";");
            out.println("    }");
            out.println();
            out.println("    /**");
            if (parameter.description.length() != 0) {
                outputWithLinebreak(out, "    ", parameter.description);
            }
            out.println("     *");
            out.println("     * @param " + parameter.name + " the " + parameter.name + " to set as {@link "
                    + getTypeClass(parameter.data_type) + "}");
            out.println("     */");
            out.println("    public void set" + upperCaseFirstCharacter(parameter.name) + "("
                    + getTypeClass(parameter.data_type) + " " + parameter.name + ") {");
            out.println("        this." + parameter.name + " = " + parameter.name + ";");
            out.println("    }");
        }

        if (className.endsWith("Request")) {
            out.println();
            out.println("    @Override");
            out.println("    public int[] serialize() {");
            out.println("        // Serialize the header");
            out.println("        serializeHeader(serializer);");
            out.println();
            out.println("        // Serialize the fields");
            for (Parameter parameter : parameters) {
                if (parameter.auto_size != null) {
                    out.println("        serializer.serialize" + getTypeSerializer(parameter.data_type) + "("
                            + parameter.auto_size + ".length);");
                    continue;
                }
                out.println("        serializer.serialize" + getTypeSerializer(parameter.data_type) + "("
                        + parameter.name + ");");
            }
            out.println("        return serializer.getPayload();");
            out.println("    }");
        } else {

        }

        out.println();
        out.println("    @Override");
        out.println("    public String toString() {");

        out.println("        final StringBuilder builder = new StringBuilder("
                + (className.length() + 3 + (parameters.size() + 1) * 25) + ");");

        out.println("        builder.append(\"" + className + " [networkId=\");");
        out.println("        builder.append(networkId);");

        for (Parameter parameter : parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println("        builder.append(\", " + parameter.name + "=\");");
            if (parameter.data_type.contains("[")) {
                out.println("        for (int cnt = 0; cnt < " + parameter.name + ".length; cnt++) {");
                out.println("            if (cnt > 0) {");
                out.println("                builder.append(' ');");
                out.println("            }");
                out.println("            builder.append(" + formatParameterString(parameter) + ");");
                out.println("        }");
            } else {
                out.println("        builder.append(" + formatParameterString(parameter) + ");");
            }
        }
        out.println("        builder.append(']');");
        out.println("        return builder.toString();");

        out.println("    }");

        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + ezspCommandPackage.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, className);

        outputCopywrite(outFile);
        outFile.println("package " + ezspCommandPackage + ";");

        outFile.println();

        outputImports(outFile);

        outFile.println();
        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }

    private void createStructureClass(Structure structure) throws FileNotFoundException, UnsupportedEncodingException {
        String className = upperCaseFirstCharacter(structure.name);
        System.out.println("Processing structure class " + structure.name + "  [" + className + "()]");

        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter, false, "UTF-8");

        clearImports();
        // addImport("org.slf4j.Logger");
        // addImport("org.slf4j.LoggerFactory");

        // addImport("java.util.Map");
        // addImport("java.util.HashMap");

        addImport(serializerPackage + ".EzspSerializer");
        addImport(serializerPackage + ".EzspDeserializer");

        out.println("/**");
        out.println(" * Class to implement the Ember Structure <b>" + structure.name + "</b>.");
        out.println(" * <p>");
        if (structure.description.length() != 0) {
            outputWithLinebreak(out, "", structure.description);
            out.println(" * <p>");
        }
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson - Initial contribution of Java code generator");
        out.println(" */");

        out.println("public class " + className + " {");

        for (Parameter parameter : structure.parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println();
            out.println("    /**");
            if (parameter.description.length() != 0) {
                outputWithLinebreak(out, "    ", parameter.description);
                out.println("     * <p>");
            }
            out.println("     * EZSP type is <i>" + parameter.data_type + "</i> - Java type is {@link "
                    + getTypeClass(parameter.data_type) + "}");
            if (parameter.multiple) {
                out.println("     * Parameter allows multiple options so implemented as a {@link Set}.");
            }
            out.println("     */");
            if (parameter.multiple) {
                addImport("java.util.Set");
                addImport("java.util.HashSet");
                out.println("    private Set<" + getTypeClass(parameter.data_type) + "> " + parameter.name
                        + " = new HashSet<" + getTypeClass(parameter.data_type) + ">();");
            } else {
                out.println("    private " + getTypeClass(parameter.data_type) + " " + parameter.name + ";");
            }
        }

        out.println();
        out.println("    /**");
        out.println("     * Default Constructor");
        out.println("     */");
        out.println("    public " + className + "() {");
        out.println("    }");

        out.println();
        out.println("    public " + className + "(EzspDeserializer deserializer) {");
        out.println("        deserialize(deserializer);");
        out.println("    }");

        for (Parameter parameter : structure.parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println();
            out.println("    /**");
            if (parameter.description.length() != 0) {
                outputWithLinebreak(out, "    ", parameter.description);
                out.println("     * <p>");
            }
            out.println("     * EZSP type is <i>" + parameter.data_type + "</i> - Java type is {@link "
                    + getTypeClass(parameter.data_type) + "}");
            out.println("     *");
            if (parameter.multiple) {
                out.println("     * @return the current " + parameter.name + " as {@link Set} of {@link "
                        + getTypeClass(parameter.data_type) + "}");
            } else {
                out.println("     * @return the current " + parameter.name + " as {@link "
                        + getTypeClass(parameter.data_type) + "}");
            }
            out.println("     */");
            if (parameter.multiple) {
                out.println("    public Set<" + getTypeClass(parameter.data_type) + "> get"
                        + upperCaseFirstCharacter(parameter.name) + "() {");
            } else {
                out.println("    public " + getTypeClass(parameter.data_type) + " get"
                        + upperCaseFirstCharacter(parameter.name) + "() {");
            }
            out.println("        return " + parameter.name + ";");
            out.println("    }");
            out.println();

            if (parameter.multiple) {
                out.println("    /**");
                if (parameter.description.length() != 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println("     *");
                out.println("     * @param " + parameter.name + " the " + parameter.name
                        + " to add to the {@link Set} as {@link " + getTypeClass(parameter.data_type) + "}");
                out.println("     */");
                out.println("    public void add" + upperCaseFirstCharacter(parameter.name) + "("
                        + getTypeClass(parameter.data_type) + " " + parameter.name + ") {");
                out.println("        this." + parameter.name + ".add(" + parameter.name + ");");
                out.println("    }");
                out.println();
                out.println("    /**");
                if (parameter.description.length() != 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println("     *");
                out.println("     * @param " + parameter.name + " the " + parameter.name
                        + " to remove to the {@link Set} as {@link " + getTypeClass(parameter.data_type) + "}");
                out.println("     */");
                out.println("    public void remove" + upperCaseFirstCharacter(parameter.name) + "("
                        + getTypeClass(parameter.data_type) + " " + parameter.name + ") {");
                out.println("        this." + parameter.name + ".remove(" + parameter.name + ");");
                out.println("    }");
            } else {
                out.println("    /**");
                if (parameter.description.length() != 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println("     *");
                out.println("     * @param " + parameter.name + " the " + parameter.name + " to set as {@link "
                        + getTypeClass(parameter.data_type) + "}");
                out.println("     */");
                out.println("    public void set" + upperCaseFirstCharacter(parameter.name) + "("
                        + getTypeClass(parameter.data_type) + " " + parameter.name + ") {");
                out.println("        this." + parameter.name + " = " + parameter.name + ";");
                out.println("    }");
            }
        }

        out.println();
        out.println("    /**");
        out.println("     * Serialise the contents of the EZSP structure.");
        out.println("     *");
        out.println("     * @param serializer the {@link EzspSerializer} used to serialize");
        out.println("     */");
        out.println("    public int[] serialize(EzspSerializer serializer) {");
        out.println("        // Serialize the fields");
        for (Parameter parameter : structure.parameters) {
            if (parameter.auto_size != null) {
                out.println("        serializer.serialize" + getTypeSerializer(parameter.data_type) + "("
                        + parameter.auto_size + ".length);");
                out.println("        serializer.serialize" + getTypeSerializer(parameter.data_type) + "("
                        + parameter.auto_size + ".length);");
                continue;
            }
            out.println("        serializer.serialize" + getTypeSerializer(parameter.data_type) + "(" + parameter.name
                    + ");");
        }
        out.println("        return serializer.getPayload();");
        out.println("    }");

        out.println();
        out.println("    /**");
        out.println("     * Deserialise the contents of the EZSP structure.");
        out.println("     *");
        out.println("     * @param deserializer the {@link EzspDeserializer} used to deserialize");
        out.println("     */");
        out.println("    public void deserialize(EzspDeserializer deserializer) {");
        out.println("        // Deserialize the fields");
        Map<String, String> autoSizers = new HashMap<String, String>();
        for (Parameter parameter : structure.parameters) {
            if (parameter.auto_size != null) {
                out.println("        int " + parameter.name + " = deserializer.deserialize"
                        + getTypeSerializer(parameter.data_type) + "();");
                autoSizers.put(parameter.auto_size, parameter.name);
                continue;
            }
            if (autoSizers.get(parameter.name) != null) {
                out.println("        " + parameter.name + "= deserializer.deserialize"
                        + getTypeSerializer(parameter.data_type) + "(" + autoSizers.get(parameter.name) + ");");
                continue;
            }
            if (parameter.data_type.contains("[") && parameter.data_type.contains("]")
                    && !parameter.data_type.contains("[]")) {
                int length = Integer.parseInt(parameter.data_type.substring(parameter.data_type.indexOf("[") + 1,
                        parameter.data_type.indexOf("]")));
                out.println("        " + parameter.name + " = deserializer.deserialize"
                        + getTypeSerializer(parameter.data_type) + "(" + length + ");");
                continue;
            }
            out.println("        " + parameter.name + " = deserializer.deserialize"
                    + getTypeSerializer(parameter.data_type) + "();");
        }
        out.println("    }");

        out.println();
        out.println("    @Override");
        out.println("    public String toString() {");
        out.println("        final StringBuilder builder = new StringBuilder("
                + (className.length() + 3 + structure.parameters.size() * 25) + ");");
        boolean first = true;
        for (Parameter parameter : structure.parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            if (first) {
                out.println("        builder.append(\"" + className + " [" + parameter.name + "=\");");
            } else {
                out.println("        builder.append(\", " + parameter.name + "=\");");
            }
            first = false;

            // If it's an array, then we want to print hex data
            if (parameter.data_type.contains("[")) {
                out.println("        builder.append('{');");
                out.println("        if (" + parameter.name + " == null) {");
                out.println("            builder.append(\"null\");");
                out.println("        } else {");
                out.println("            for (int cnt = 0; cnt < " + parameter.name + ".length; cnt++) {");
                out.println("                if (cnt != 0) {");
                out.println("                    builder.append(' ');");
                out.println("                }");
                // if(formatParameterString(parameter)==)
                out.println("                builder.append(" + formatParameterString(parameter) + ");");
                out.println("            }");
                out.println("        }");
                out.println("        builder.append('}');");
            } else {
                out.println("        builder.append(" + formatParameterString(parameter) + ");");
            }
        }
        out.println("        builder.append(']');");
        out.println("        return builder.toString();");
        out.println("    }");

        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + ezspStructurePackage.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, className);

        outputCopywrite(outFile);
        outFile.println("package " + ezspStructurePackage + ";");

        outFile.println();

        outputImports(outFile);

        outFile.println();
        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }

    private void createEnumClass(Enumeration enumeration) throws FileNotFoundException, UnsupportedEncodingException {
        String className = upperCaseFirstCharacter(enumeration.name);
        System.out.println("Processing enum class " + enumeration.name + "  [" + className + "()]");

        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter, false, "UTF-8");

        clearImports();

        addImport("java.util.Map");
        addImport("java.util.HashMap");

        out.println("/**");
        out.println(" * Class to implement the Ember Enumeration <b>" + enumeration.name + "</b>.");
        if (enumeration.description != null && enumeration.description.trim().length() > 0) {
            out.println(" * <p>");
            outputWithLinebreak(out, "", enumeration.description);
        }
        out.println(" * <p>");
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson - Initial contribution of Java code generator");
        out.println(" */");

        out.println("public enum " + className + " {");

        out.println("    /**");
        out.println("     * Default unknown value");
        out.println("     */");
        out.println("    UNKNOWN(-1),");

        boolean first = true;
        for (Value value : enumeration.values) {
            if (!first) {
                out.println(",");
            }
            first = false;
            out.println();
            out.println("    /**");
            if (value.description.length() != 0) {
                outputWithLinebreak(out, "    ", value.description);
            }
            out.println("     */");
            out.print("    " + value.name + "(0x" + String.format("%04X", value.enum_value) + ")");
        }

        out.println(";");

        out.println();
        out.println("    /**");
        out.println("     * A mapping between the integer code and its corresponding type to");
        out.println("     * facilitate lookup by code.");
        out.println("     */");
        out.println("    private static Map<Integer, " + className + "> codeMapping;");
        out.println();

        out.println("    private int key;");
        out.println();

        out.println("    static {");
        out.println("        codeMapping = new HashMap<Integer, " + className + ">();");
        out.println("        for (" + className + " s : values()) {");
        out.println("            codeMapping.put(s.key, s);");
        out.println("        }");
        out.println("    }");
        out.println();

        out.println("    private " + className + "(int key) {");
        out.println("        this.key = key;");
        out.println("    }");
        out.println();

        out.println("    /**");
        out.println("     * Lookup function based on the EmberStatus type code. Returns null if the");
        out.println("     * code does not exist.");
        out.println("     *");
        out.println("     * @param code the code to lookup");
        out.println("     * @return enumeration value of the alarm type.");
        out.println("     */");
        out.println("    public static " + className + " get" + className + "(int code) {");
        out.println("        if (codeMapping.get(code) == null) {");
        out.println("            return UNKNOWN;");
        out.println("        }");
        out.println();

        out.println("        return codeMapping.get(code);");
        out.println("    }");
        out.println();
        out.println("    /**");
        out.println("     * Returns the EZSP protocol defined value for this enumeration.");
        out.println("     *");
        out.println("     * @return the EZSP protocol key");
        out.println("     */");
        out.println("    public int getKey() {");
        out.println("        return key;");
        out.println("    }");

        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + ezspStructurePackage.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, className);

        outputCopywrite(outFile);
        outFile.println("package " + ezspStructurePackage + ";");

        outFile.println();

        outputImports(outFile);

        outFile.println();
        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }

    protected String getTypeClass(String dataType) {
        String modifier = "";
        String dataTypeLocal = new String(dataType);
        if (dataType.contains("[")) {
            dataTypeLocal = dataTypeLocal.substring(0, dataTypeLocal.indexOf("["));
            modifier = "[]";
        }

        switch (dataTypeLocal) {
            case "bool":
                return "boolean";
            case "EmberNodeId":
            case "EmberCounterType":
            case "EmberGpSecurityFrameCounter":
            case "EzspDecisionBitmask":
            case "int8s":
            case "uint8_u":
            case "uint8_t":
            case "uint16_t":
            case "uint32_t":
                return "int" + modifier;
            // }
            // int size = Integer.parseInt(dataType.substring(dataType.indexOf("[") + 1, dataType.indexOf("]")));
            // return "int[" + size + "]";
            case "ExtendedPanId":
                addImport("com.zsmartsystems.zigbee.ExtendedPanId");
                return "ExtendedPanId";
            case "EmberEUI64":
                addImport("com.zsmartsystems.zigbee.IeeeAddress");
                return "IeeeAddress";
            case "EmberRouteTableEntry":
                addImport(ezspStructurePackage + ".EmberRouteTableEntry");
                return "EmberRouteTableEntry";
            case "EmberBindingTableEntry":
                addImport(ezspStructurePackage + ".EmberBindingTableEntry");
                return "EmberBindingTableEntry";
            case "EmberZigbeeNetwork":
                addImport(ezspStructurePackage + ".EmberZigbeeNetwork");
                return "EmberZigbeeNetwork";
            case "EmberNeighborTableEntry":
                addImport(ezspStructurePackage + ".EmberNeighborTableEntry");
                return "EmberNeighborTableEntry";
            case "EmberInitialSecurityState":
                addImport(ezspStructurePackage + ".EmberInitialSecurityState");
                return "EmberInitialSecurityState";
            case "EmberCurrentSecurityState":
                addImport(ezspStructurePackage + ".EmberCurrentSecurityState");
                return "EmberCurrentSecurityState";
            case "EmberOutgoingMessageType":
                addImport(ezspStructurePackage + ".EmberOutgoingMessageType");
                return "EmberOutgoingMessageType";
            case "EzspDecisionId":
                addImport(ezspStructurePackage + ".EzspDecisionId");
                return "EzspDecisionId";
            case "EmberIncomingMessageType":
                addImport(ezspStructurePackage + ".EmberIncomingMessageType");
                return "EmberIncomingMessageType";
            case "EmberApsFrame":
                addImport(ezspStructurePackage + ".EmberApsFrame");
                return "EmberApsFrame";
            case "EzspPolicyId":
                addImport(ezspStructurePackage + ".EzspPolicyId");
                return "EzspPolicyId";
            case "EzspNetworkScanType":
                addImport(ezspStructurePackage + ".EzspNetworkScanType");
                return "EzspNetworkScanType";
            case "EzspStatus":
                addImport(ezspStructurePackage + ".EzspStatus");
                return "EzspStatus";
            case "EmberStatus":
                addImport(ezspStructurePackage + ".EmberStatus");
                return "EmberStatus";
            case "EmberNetworkParameters":
                addImport(ezspStructurePackage + ".EmberNetworkParameters");
                return "EmberNetworkParameters";
            case "EmberNodeType":
                addImport(ezspStructurePackage + ".EmberNodeType");
                return "EmberNodeType";
            case "EzspConfigId":
                addImport(ezspStructurePackage + ".EzspConfigId");
                return "EzspConfigId";
            case "EmberNetworkStatus":
                addImport(ezspStructurePackage + ".EmberNetworkStatus");
                return "EmberNetworkStatus";
            case "EmberKeyData":
                addImport(ezspStructurePackage + ".EmberKeyData");
                return "EmberKeyData";
            case "EmberKeyType":
                addImport(ezspStructurePackage + ".EmberKeyType");
                return "EmberKeyType";
            case "EmberDeviceUpdate":
                addImport(ezspStructurePackage + ".EmberDeviceUpdate");
                return "EmberDeviceUpdate";
            case "EmberJoinDecision":
                addImport(ezspStructurePackage + ".EmberJoinDecision");
                return "EmberJoinDecision";
            case "EmberMacPassthroughType":
                addImport(ezspStructurePackage + ".EmberMacPassthroughType");
                return "EmberMacPassthroughType";
            case "EmberConcentratorType":
                addImport(ezspStructurePackage + ".EmberConcentratorType");
                return "EmberConcentratorType";
            case "EzspValueId":
                addImport(ezspStructurePackage + ".EzspValueId");
                return "EzspValueId";
            case "EmberKeyStruct":
                addImport(ezspStructurePackage + ".EmberKeyStruct");
                return "EmberKeyStruct";
            case "EmberPowerMode":
                addImport(ezspStructurePackage + ".EmberPowerMode");
                return "EmberPowerMode";
            case "EmberGpAddress":
                addImport(ezspStructurePackage + ".EmberGpAddress");
                return "EmberGpAddress";
            case "EmberGpSecurityLevel":
                addImport(ezspStructurePackage + ".EmberGpSecurityLevel");
                return "EmberGpSecurityLevel";
            case "EmberGpKeyType":
                addImport(ezspStructurePackage + ".EmberGpKeyType");
                return "EmberGpKeyType";
            case "EmberGpProxyTableEntry":
                addImport(ezspStructurePackage + ".EmberGpProxyTableEntry");
                return "EmberGpProxyTableEntry";
            case "EmberGpSinkListEntry[":
                addImport(ezspStructurePackage + ".EmberGpSinkListEntry");
                return "EmberGpSinkListEntry";
            case "EmberAesMmoHashContext":
                addImport(ezspStructurePackage + ".EmberAesMmoHashContext");
                return "EmberAesMmoHashContext";
            case "EzspMfgTokenId":
            case "EmberCertificateData":
            case "EmberCertificate283k1Data":
            case "EmberPublicKeyData":
            case "EmberPublicKey283k1Data":
            case "EmberPrivateKeyData":
            case "EmberPrivateKey283k1Data":
            case "EmberSmacData":
            case "EmberLibraryId":
            case "EmberLibraryStatus":
            case "EmberTransientKeyData":
            case "EmberGpSinkListEntry":
            case "EmberGpSinkTableEntry":
            case "EmberBeaconData":
            case "EmberBeaconIterator":
                addImport(ezspStructurePackage + "." + dataTypeLocal);
                return dataTypeLocal + modifier;
            default:
                return dataTypeLocal + modifier;
        }
    }

    protected String getTypeSerializer(String dataType) {
        String dataTypeLocal = new String(dataType);
        if (dataType.contains("[")) {
            dataTypeLocal = dataTypeLocal.substring(0, dataTypeLocal.indexOf("[") + 1);
        }
        switch (dataTypeLocal) {
            case "EmberCounterType":
            case "uint8_t":
            case "uint8_u":
                return "UInt8";
            case "EzspDecisionBitmask":
            case "EmberNodeId":
            case "uint16_t":
                return "UInt16";
            case "uint32_t":
            case "EmberGpSecurityFrameCounter":
                return "UInt32";
            case "uint8_t[":
            case "uint8_u[":
                return "UInt8Array";
            case "int8s":
                return "Int8S";
            case "uint16_t[":
                return "UInt16Array";
            case "Bool":
                return "Boolean";
            case "EmberEUI64":
                return "EmberEui64";
            case "bool":
                return "Bool";
            case "EmberAesMmoHashContext":
                return "EmberAesMmoHashContext";
            case "EzspValueId":
                return "EzspValueId";
            case "EmberKeyStruct":
                return "EmberKeyStruct";
            case "EmberPowerMode":
                return "EmberPowerMode";
            case "ExtendedPanId":
                return "ExtendedPanId";
            case "EmberGpAddress":
                return "EmberGpAddress";
            case "EmberGpSecurityLevel":
                return "EmberGpSecurityLevel";
            case "EmberGpKeyType":
                return "EmberGpKeyType";
            case "EmberGpProxyTableEntry":
                return "EmberGpProxyTableEntry";
            case "EmberGpSinkListEntry[":
                return "EmberGpSinkListEntry";
            case "EzspMfgTokenId":
            case "EmberLibraryId":
            case "EmberLibraryStatus":
            case "EmberCertificateData":
            case "EmberCertificate283k1Data":
            case "EmberPublicKeyData":
            case "EmberPublicKey283k1Data":
            case "EmberPrivateKeyData":
            case "EmberPrivateKey283k1Data":
            case "EmberSmacData":
            case "EmberTransientKeyData":
            case "EmberGpSinkListEntry":
            case "EmberGpSinkTableEntry":
            case "EmberBeaconData":
            case "EmberBeaconIterator":
                return dataTypeLocal;
            default:
                return dataType;
        }
    }

    private void createEmberFrame(Protocol protocol) throws FileNotFoundException, UnsupportedEncodingException {
        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter);

        clearImports();

        outputCopywrite(out);

        out.println("package " + ezspPackage + ";");
        out.println();
        out.println("import java.lang.reflect.Constructor;");
        out.println("import java.lang.reflect.InvocationTargetException;");
        out.println("import java.util.HashMap;");
        out.println("import java.util.Map;");
        out.println();
        out.println("import org.slf4j.Logger;");
        out.println("import org.slf4j.LoggerFactory;");
        // out.println();
        // out.println("import " + ashPackage + ".AshFrameData;");

        Map<String, Command> commandMap = new TreeMap<>();
        for (Command command : protocol.commands) {
            commandMap.put(command.name, command);
        }

        addImport(ezspCommandPackage + ".*");

        outputImports(out);
        out.println();

        out.println("/**");
        out.println(
                " * The EmberZNet Serial Protocol (EZSP) is the protocol used by a host application processor to interact with the");
        out.println(" * EmberZNet PRO stack running on a Network CoProcessor (NCP).");
        out.println(" * <p>");
        out.println(" * Reference: UG100: EZSP Reference Guide");
        out.println(" * <p>");
        out.println(" * An EZSP V4 Frame is made up as follows -:");
        out.println(" * <ul>");
        out.println(" * <li>Sequence : 1 byte sequence number");
        out.println(" * <li>Frame Control: 1 byte");
        out.println(" * <li>Frame ID : 1 byte");
        out.println(" * <li>Parameters : variable length");
        out.println(" * </ul>");
        out.println(" * <p>");
        out.println(" * An EZSP V5+ Frame is made up as follows -:");
        out.println(" * <ul>");
        out.println(" * <li>Sequence : 1 byte sequence number");
        out.println(" * <li>Frame Control: 1 byte");
        out.println(" * <li>Legacy Frame ID : 1 byte");
        out.println(" * <li>Extended Frame Control : 1 byte");
        out.println(" * <li>Frame ID : 1 byte");
        out.println(" * <li>Parameters : variable length");
        out.println(" * </ul>");
        out.println(" *");
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson");
        out.println(" */");
        out.println("public abstract class EzspFrame {");
        out.println("    /**");
        out.println("     * The logger");
        out.println("     */");
        out.println("    private final static Logger logger = LoggerFactory.getLogger(EzspFrame.class);");
        out.println();
        out.println("    /**");
        out.println("     * The minimum supported version of EZSP");
        out.println("     */");
        out.println("    private static final int EZSP_MIN_VERSION = 4;");
        out.println();
        out.println("    /**");
        out.println("     * The maximum supported version of EZSP");
        out.println("     */");
        out.println("    private static final int EZSP_MAX_VERSION = 8;");
        out.println();
        out.println("    /**");
        out.println("     * The network ID bit shift");
        out.println("     */");
        out.println("    protected static final int EZSP_NETWORK_ID_SHIFT = 5;");
        out.println();
        out.println("    /**");
        out.println("     * The network ID bit mask");
        out.println("     */");
        out.println("    protected static final int EZSP_NETWORK_ID_MASK = 0x60;");
        out.println();
        out.println("    /**");
        out.println("     * The current version of EZSP being used");
        out.println("     */");
        out.println("    protected static int ezspVersion = EZSP_MIN_VERSION;");
        out.println();
        out.println("    /**");
        out.println("     * Legacy frame ID for EZSP 5+");
        out.println("     */");
        out.println("    protected static final int EZSP_LEGACY_FRAME_ID = 0xFF;");
        out.println();
        out.println("    /**");
        out.println("     * EZSP Frame Control Request flag");
        out.println("     */");
        out.println("    protected static final int EZSP_FC_REQUEST = 0x0000;");
        out.println();
        out.println("    /**");
        out.println("     * EZSP Frame Control Response flag");
        out.println("     */");
        out.println("    protected static final int EZSP_FC_RESPONSE = 0x0080;");
        out.println();
        for (Command command : commandMap.values()) {
            String reference = camelCaseToConstant(
                    command.name.substring(0, 1).toUpperCase() + command.name.substring(1));
            out.println("    protected static final int FRAME_ID_" + reference + " = 0x"
                    + String.format("%02X", command.id) + ";");
        }

        out.println();

        out.println("    protected int sequenceNumber;");
        out.println("    protected int frameControl;");
        out.println("    protected int frameId = 0;");
        out.println("    protected int networkId = 0;");
        out.println("    protected boolean isResponse = false;");
        out.println();
        out.println("    private static Map<Integer, Class<?>> ezspHandlerMap = new HashMap<Integer, Class<?>>();");
        out.println("    static {");
        for (Command command : commandMap.values()) {
            String className;
            if (command.name.endsWith("Handler")) {
                className = "Ezsp" + command.name.substring(0, 1).toUpperCase() + command.name.substring(1);
            } else {
                className = "Ezsp" + command.name.substring(0, 1).toUpperCase() + command.name.substring(1)
                        + "Response";
            }

            String reference = camelCaseToConstant(
                    command.name.substring(0, 1).toUpperCase() + command.name.substring(1));
            out.println("        ezspHandlerMap.put(FRAME_ID_" + reference + ", " + className + ".class);");
        }
        out.println("    }");
        out.println();

        out.println("    /**");
        out.println("     * Sets the network ID (0 to 3)");
        out.println("     *");
        out.println("     * @param networkId the networkId");
        out.println("     */");
        out.println("    public void setNetworkId(int networkId) {");
        out.println("        this.networkId = networkId;");
        out.println("    }");
        out.println();
        out.println("    /**");
        out.println("     * Gets the network ID (0 to 3)");
        out.println("     *");
        out.println("     * @return the networkId of this frame");
        out.println("     */");
        out.println("    public int getNetworkId() {");
        out.println("        return networkId;");
        out.println("    }");
        out.println();
        out.println("    /**");
        out.println("     * Gets the 8 bit transaction sequence number");
        out.println("     *");
        out.println("     * @return sequence number");
        out.println("     */");
        out.println("    public int getSequenceNumber() {");
        out.println("        return sequenceNumber;");
        out.println("    }");
        out.println();
        out.println("    /**");
        out.println("     * Checks if this frame is a response frame");
        out.println("     *");
        out.println("     * @return true if this is a response");
        out.println("     */");
        out.println("    public boolean isResponse() {");
        out.println("        return isResponse;");
        out.println("    }");
        out.println();
        out.println("    /**");
        out.println("     * Gets the Ember frame ID for this frame");
        out.println("     *");
        out.println("     * @return the Ember frame Id");
        out.println("     */");
        out.println("    public int getFrameId() {");
        out.println("        return frameId;");
        out.println("    }");
        out.println();
        out.println("    /**");
        out.println("     * Creates and {@link EzspFrameResponse} from the incoming data.");
        out.println("     *");
        out.println("     * @param data the int[] containing the EZSP data from which to generate the frame");
        out.println("     * @return the {@link EzspFrameResponse} or null if the response can't be created.");
        out.println("     */");
        out.println("    public static EzspFrameResponse createHandler(int[] data) {");
        out.println("        int frameId;");
        out.println();
        out.println("        try {");
        out.println("            if (ezspVersion >= 8) {");
        out.println("                frameId = data[3] + (data[4] << 8);");
        out.println("            } else {");
        out.println("                if (data[2] != 0xFF) {");
        out.println("                    frameId = data[2];");
        out.println("                } else {");
        out.println("                    frameId = data[4];");
        out.println("                }");
        out.println("            }");
        out.println("        } catch (ArrayIndexOutOfBoundsException e) {");
        out.println("            logger.debug(\"Error detecting the EZSP frame type\", e);");
        out.println("            return null;");
        out.println("        }");
        out.println();
        out.println("        Class<?> ezspClass = ezspHandlerMap.get(frameId);");
        out.println("        if (ezspClass == null) {");
        out.println("            return null;");
        out.println("        }");
        out.println();
        out.println("        Constructor<?> ctor;");
        out.println("        try {");
        out.println("            ctor = ezspClass.getConstructor(int[].class);");
        out.println("            EzspFrameResponse ezspFrame = (EzspFrameResponse) ctor.newInstance(data);");
        out.println("            return ezspFrame;");
        out.println(
                "        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException | InstantiationException");
        out.println("                | IllegalAccessException | InvocationTargetException e) {");
        out.println("            logger.debug(\"Error creating instance of EzspFrame\", e);");
        out.println("        }");
        out.println();
        out.println("        return null;");
        out.println("    }");
        out.println();

        out.println("    /**");
        out.println("     * Set the EZSP version to use");
        out.println("     *");
        out.println("     * @param ezspVersion the EZSP protocol version");
        out.println("     * @return true if the version is supported");
        out.println("     */");
        out.println("    public static boolean setEzspVersion(int ezspVersion) {");
        out.println("        if (ezspVersion <= EZSP_MAX_VERSION && ezspVersion >= EZSP_MIN_VERSION) {");
        out.println("            EzspFrame.ezspVersion = ezspVersion;");
        out.println("            return true;");
        out.println("        }");
        out.println();
        out.println("        return false;");
        out.println("    }");
        out.println();

        out.println("    /**");
        out.println(
                "     * Gets the current version of EZSP that is in use. This will default to the minimum supported version on startup");
        out.println("     *");
        out.println("     * @return the current version of EZSP");
        out.println("     */");
        out.println("    public static int getEzspVersion() {");
        out.println("        return EzspFrame.ezspVersion;");
        out.println("    }");

        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + ezspPackage.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, "EzspFrame");

        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }
}
